#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>

#define EPOLL_MAX_EVENTS 5

volatile sig_atomic_t got_signal = 0;

void sigio_handler(int signo) {
    printf("Received SIGIO signal: %d\n", signo);
    if (signo == SIGIO) {
        got_signal = 1;
    }
}

void handle_io_event(int fd) {
    printf("Handling I/O event on fd %d\n", fd);
    int epoll_fd;
    struct epoll_event event;
    struct epoll_event events[EPOLL_MAX_EVENTS];
    int nfds, n;

    if (fd < 0) {
        return;
    }
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;
    epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
        perror("epoll_create1");
        return;
    }

    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) == -1) {
        perror("epoll_ctl");
        close(epoll_fd);
        return;
    }
    nfds = epoll_wait(epoll_fd, events, EPOLL_MAX_EVENTS, 5000);
    if (nfds == -1) {
        perror("epoll_wait");
        close(epoll_fd);
        return;
    } else if (nfds == 0) {
        printf("No data within 5 second.\n");
    } else {
        for (n = 0; n < nfds; ++n) {
            if (events[n].data.fd == fd) {
                char buf[100] = {0};
                ssize_t ret = read(fd, buf, sizeof(buf) - 1);
                if (ret < 0) {
                    perror("read");
                } else if (ret == 0) {
                    printf("End of file reached.\n");
                } else {
                    printf("Read %zd bytes: %s\n", ret, buf);
                }
            }
        }
    }
    close(epoll_fd);
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <device>\n", argv[0]);
        return EXIT_FAILURE;
    }

    const char *device = argv[1];
    int fd = open(device, O_RDWR | O_NONBLOCK);
    if (fd < 0) {
        perror("Failed to open device");
        return EXIT_FAILURE;
    }

    // Set up the SIGIO signal handler
    struct sigaction sa;
    sa.sa_handler = sigio_handler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGIO, &sa, NULL) < 0) {
        perror("Failed to set up SIGIO handler");
        close(fd);
        return EXIT_FAILURE;
    }

    // Enable asynchronous notification
    if (fcntl(fd, F_SETOWN, getpid()) < 0) {
        perror("Failed to set process owner");
        close(fd);
        return EXIT_FAILURE;
    }
    int flags = fcntl(fd, F_GETFL);
    if (fcntl(fd, F_SETFL, flags | O_ASYNC) < 0) {
        perror("Failed to enable O_ASYNC");
        close(fd);
        return EXIT_FAILURE;
    }

    printf("Waiting for data on %s...\n", device);

    // Main loop to keep the program running
    while (1) {
        pause(); // Wait for signals
        printf("Signal received, checking for data...\n");
        if (got_signal) {
            got_signal = 0;
            handle_io_event(fd);
        }
    }

    close(fd);
    return EXIT_SUCCESS;
}